/**************************************************************************\
    Copyright Microsoft Corporation. All Rights Reserved.
\**************************************************************************/

namespace Microsoft.ContactsBridge
{
    using Microsoft.ContactsBridge.Interop;
    using Standard;
    using System;

    /// <summary>
    /// The different types of values a ContactProperty can represent.
    /// </summary>
    public enum ContactPropertyType
    {
        /// <summary>
        /// Default type.  Valid contact properties should not be of this type.
        /// </summary>
        None = 0,

        /// <summary>
        /// The array node type.  This represents a node in an hierarchical property set.
        /// </summary>
        /// <remarks>
        /// There is no valid Getter for this type.
        /// </remarks>
        ArrayNode = 1,

        /// <summary>
        /// The property type is a string.  Use GetStringProperty to retrieve the value.
        /// </summary>
        String = 2,

        /// <summary>
        /// The property type is a date.  Use GetDateProperty to retrieve the value.
        /// </summary>
        DateTime = 3,

        /// <summary>
        /// The property type is a binary stream and string type.  Use GetBinaryProperty to retrieve the value.
        /// </summary>
        Binary = 4,
    }

    /// <summary>
    /// Information about a specific property in a contact.
    /// </summary>
    /// <remarks>
    /// These are usually gotten through Contact's GetPropertyCollection enumerator.
    /// </remarks>
    public struct ContactProperty : IEquatable<ContactProperty>
    {
        #region Fields
        private DateTime _modified;
        private Guid _id;
        private int _version;
        private string _name;
        private ContactPropertyType _type;
        #endregion

        internal ContactProperty(IContactPropertyCollection propertyNode)
        {
            Assert.IsNotNull(propertyNode);

            ContactUtil.GetPropertyModificationDate(propertyNode, out _modified)
                .ThrowIfFailed("Couldn't get the modification date for this propertyName.");

            _type = ContactPropertyType.None;
            uint dwType;
            ContactUtil.GetPropertyType(propertyNode, out dwType).ThrowIfFailed();
            HRESULT hr = Win32Error.ERROR_INVALID_DATATYPE;
            switch (dwType)
            {
                case ContactValue.CGD_ARRAY_NODE:
                    _type = ContactPropertyType.ArrayNode;
                    hr = HRESULT.S_OK;
                    break;
                case ContactValue.CGD_BINARY_PROPERTY:
                    _type = ContactPropertyType.Binary;
                    hr = HRESULT.S_OK;
                    break;
                case ContactValue.CGD_DATE_PROPERTY:
                    _type = ContactPropertyType.DateTime;
                    hr = HRESULT.S_OK;
                    break;
                case ContactValue.CGD_STRING_PROPERTY:
                    _type = ContactPropertyType.String;
                    hr = HRESULT.S_OK;
                    break;
            }
            hr.ThrowIfFailed("Couldn't get the type for this property.");

            ContactUtil.GetPropertyName(propertyNode, out _name)
                .ThrowIfFailed("Couldn't get the name for this property.");

            ContactUtil.GetPropertyVersion(propertyNode, out _version)
                .ThrowIfFailed("Couldn't get the version number for this property.");

            // IDs are only valid on ArrayNode types.
            _id = default(Guid);
            if (ContactPropertyType.ArrayNode == _type)
            {
                ContactUtil.GetPropertyID(propertyNode, out _id)
                    .ThrowIfFailed("Couldn't get the identifier for this array array-node.");
            }
        }

        /// <summary>
        /// Get the timestamp for when this property was last modified.
        /// </summary>
        public DateTime ModificationDate
        {
            get
            {
                return _modified;
            }
        }

        /// <summary>
        /// Get the unique identifier for this property.
        /// This is only meaningful for properties of type ArrayNode.
        /// </summary>
        /// <remarks>
        /// ArrayNode properties have Guids associated with them to help sync
        /// replicators recognize when two nodes came from the same source.
        /// </remarks>
        public Guid ElementId
        {
            get
            {
                return _id;
            }
        }

        /// <summary>
        /// Get the version number for this property.  New properties have a version of 1.
        /// </summary>
        public int Version
        {
            get
            {
                return _version;
            }
        }

        /// <summary>
        /// Get the name for this property.  This can be used in conjunction with the Contact to get the value.
        /// </summary>
        /// <remarks>
        /// If this property has a ContactPropertyType of String, then to get the value you can call:
        /// <code>
        /// string propValue = contact.GetStringProperty(property.Name);
        /// </code>
        /// If this property has a ContactPropertyType of DateTime, then to get the value you can call:
        /// <code>
        /// DateTime? propTime = contact.GetDateProperty(property.Name);
        /// </code>
        /// If this property has a ContactPropertyType of Binary, then to get the value you can call:
        /// <code>
        /// StringBuilder propType = new StringBuilder();
        /// Stream propData = contact.GetBinaryProperty(property.Name, propType);
        /// </code>
        /// </remarks>
        public string Name
        {
            get
            {
                return _name ?? "";
            }
        }

        /// <summary>
        /// Get the type of this property.
        /// This tells you what types of data are queryable for this property.
        /// </summary>
        public ContactPropertyType Type
        {
            get
            {
                return _type;
            }
        }

        #region Object Overrides

        public override bool Equals(object obj)
        {
            try
            {
                return this.Equals((ContactProperty)obj);
            }
            catch (InvalidCastException)
            {
                return false;
            }
        }

        public override int GetHashCode()
        {
            return ElementId.GetHashCode() ^ Name.GetHashCode();
        }

        public override string ToString()
        {
            return string.Format(null, "Name=\"{0}\", Type=\"{1}\", Version=\"{2}\", ElementId=\"{3}\", ModificationDate=\"{4}\"",
                Name,
                Type,
                Version,
                ElementId,
                ModificationDate);
        }

        #endregion

        public static bool operator ==(ContactProperty left, ContactProperty right)
        {
            return left.Equals(right);
        }

        public static bool operator !=(ContactProperty left, ContactProperty right)
        {
            return !(left == right);
        }

        #region IEquatable<ContactProperty> Members

        public bool Equals(ContactProperty other)
        {
            return this.ElementId == other.ElementId
                && this.ModificationDate == other.ModificationDate
                // case sensitive:
                && (0 == string.Compare(this.Name, other.Name, StringComparison.InvariantCulture))
                && this.Type == other.Type
                && this.Version == other.Version;
        }

        #endregion
    }
}
